/** @file
@brief Declarations related to #PrynTag. */
#ifndef PRYN_TAG_H
#define PRYN_TAG_H

#include <pryn/platform.h>

#include <pryn/common.h>
#include <pryn/object.h>
#include <pryn/state.h>
#include <pryn/string.h>

/** @defgroup PrynTag PrynTag C API
@{ */

typedef PrynResult (*PrynTagDestroyFunction) (PrynTag *tag); ///< Function signature to destroy a tag.
typedef PrynResult (*PrynTagMatchFunction) (PrynTag *tag, const PrynString *id, void *key); ///< Function signature to identify a tag, returning #PrynResultSuccess on a successful match or #PrynResultDone if non-matching.

PrynImport (PrynResult, prynTagFindByMatch, (PrynTag **tag, PrynTagList *list, const PrynString *id, void *key)) ///< Find a tag, potentially matching only against tags with match functions. Returns #PrynResultDone if not found.
PrynImport (PrynResult, prynTagFindByKey, (PrynTag **tag, PrynTagList *list, void *key)) ///< Find a tag, potentially matching only against tags with a key and no match function. Returns #PrynResultDone if not found.
PrynImport (PrynResult, prynTagFindById, (PrynTag **tag, PrynTagList *list, const PrynString *id)) ///< Find a tag, potentially matching only against tags with no key and no match function. Returns #PrynResultDone if not found.

PrynImport (PrynResult, prynTagFindDataByMatch, (void **data, PrynTagList *list, const PrynString *id, void *key)) ///< Find a tag, potentially matching only against tags with match functions. Returns #PrynResultDone if not found.
PrynImport (PrynResult, prynTagFindDataByKey, (void **data, PrynTagList *list, void *key)) ///< Find a tag, potentially matching only against tags with a key and no match function. Returns #PrynResultDone if not found.
PrynImport (PrynResult, prynTagFindDataById, (void **data, PrynTagList *list, const PrynString *id)) ///< Find a tag, potentially matching only against tags with no key and no match function. Returns #PrynResultDone if not found.

PrynImport (PrynResult, prynTagCreate, (PrynTag **tag, PrynObject *object, const PrynString *id, void *data))
	/**< @brief Create a new tag.

	@param[out] tag Written with the newly created tag or 0 on failure.
	@param object The object to assign the tag to. It must be an object type that has tags - #prynObjectTags (object) would be non-zero.
	@param id How to identify the tag.
	@param data An opaque data pointer applied to the tag.
	@returns #PrynResultSuccess or an error code; see #PrynResult. */

PrynImport (PrynResult, prynTagCreateWithKey, (PrynTag **tag, PrynObject *object, void *key, const PrynString *id, void *data))
	/**< @brief Create a new tag with a key. 

	@param[out] tag Written with the newly created tag or 0 on failure.
	@param object The object to assign the tag to. It must be an object type that has tags - #prynObjectTags (object) would be non-zero.
	@param key An opaque data pointer used for matching the tag.
	@param id How to identify the tag.
	@param data An opaque data pointer applied to the tag.
	@returns #PrynResultSuccess or an error code; see #PrynResult. */

PrynImport (PrynResult, prynTagToObject, (PrynTag *tag, PrynObject *object)) ///< Wrap the tag into an object.

PrynImport (PrynResult, prynTagDestroy, (PrynTag *tag)) ///< Destroy the tag. After calling this function, this tag pointer must not be used.

PrynImport (PrynTagDestroyFunction, prynTagFreeDataFunction, ()) ///< Return a default function for destroying a tag by deleting the data.

PrynImport (PrynResult, prynTagState, (PrynTag *tag, PrynState **output)) ///< Return the state for the tag, or 0 if tag is zero.
PrynImport (PrynResult, prynTagObject, (PrynTag *tag, PrynObject *output)) ///< Return the object the tag is within, or 0 if tag is zero.
PrynImport (PrynResult, prynTagList, (PrynTag *tag, PrynTagList **output)) ///< Return the tag list the tag is within, or 0 if tag is zero.
PrynImport (PrynResult, prynTagData, (PrynTag *tag, void **output)) ///< Return the tag data, or 0 if tag is zero.
PrynImport (PrynResult, prynTagKey, (PrynTag *tag, void **output)) ///< Return the tag key, or 0 if tag is zero.
PrynImport (PrynResult, prynTagTags, (PrynTag *tag, PrynTagList **output)) ///< Return the tag list on the tag, or 0 if tag is zero.

PrynImport (PrynResult, prynTagSetMatchFunction, (PrynTag *tag, PrynTagMatchFunction match)) ///< Make the tag match by id.
PrynImport (PrynResult, prynTagSetKey, (PrynTag *tag, void *key)) ///< Set the key on the tag. This does not set match to 0.
PrynImport (PrynResult, prynTagSetDestroyFunction, (PrynTag *tag, PrynTagDestroyFunction destroy)) ///< Set the destruction function.
PrynImport (PrynResult, prynTagSetData, (PrynTag *tag, void *data)) ///< Set the data value on the tag.

PrynImport (PrynResult, prynTagListDestroy, (PrynTagList *list)) ///< Destroy all the tags in the list and zero the list.

#ifdef PrynInternalStructs
/** @brief A user-defined piece of data that can be attached to most objects.
	@ingroup PrynTag
*/
struct PrynTag
{
#ifdef PrynInternal
/** @name Internal fields
	These fields are not normally visible to clients, and it is recommended not to use them to maximise binary compatibility. To make them available, define PrynInternal.
	@{ */

	PrynCommonObject pCommonObject; ///< Common fields you'd expect in an object, like tags and monitors.

	PrynTag *pNext; ///< Next tag on the object or 0 if this is the last.
	PrynTag *pPrevious; ///< Previous tag on the object or 0 if this is the first.
	PrynObject pObject; ///< The object that this is connected to.
	
	PrynTagMatchFunction pMatchFunction; ///< Return whether this tag matches the id or key. If this is 0, then key is used instead.
	void *pKey; ///< A user-defined key. A tag matches if the key is the same. If this is 0, then id is used instead.
	PrynString pId; ///< An identifier for the tag. This can be empty if key or match are defined instead.
	
	void *pData; ///< The user-defined data this tag refers to.
	PrynTagDestroyFunction pDestroyFunction; ///< Destroy the data and the key if necessary.

/** @} */
#endif /* PrynInternal */

#if __cplusplus
	static inline PrynTag *Create (PrynObject object, const PrynString &id, void *data);
		/**< @brief Create a new tag.

		@param object The object to assign the tag to. It must be an object type that has tags - #prynObjectTags (object) would be non-zero.
		@param id How to identify the tag.
		@param data An opaque data pointer applied to the tag.
		@returns The newly created tag. */

	static inline PrynTag *Create (PrynObject object, void *key, const PrynString &id, void *data) { PrynTag *result; object.checkError (prynTagCreateWithKey (&result, &object, key, &id, data)); return result; }
		/**< @brief Create a new tag with a key. 

		@param object The object to assign the tag to. It must be an object type that has tags - #prynObjectTags (object) would be non-zero.
		@param key An opaque data pointer used for matching the tag.
		@param id How to identify the tag.
		@param data An opaque data pointer applied to the tag.
		@returns The newly created tag. */

	static inline PrynTag *FindByMatch (PrynTagList *list, const PrynString &id, void *key) { PrynTag *tag; prynCheckErrorNull (prynTagFindByMatch (&tag, list, &id, key)); return tag; } ///< Find a tag, potentially matching only against tags with match functions. Returns 0 if not found.

	static inline PrynTag *FindByKey (PrynTagList *list, void *key) { PrynTag *tag; prynCheckErrorNull (prynTagFindByKey (&tag, list, key)); return tag; } ///< Find a tag, potentially matching only against tags with a key and no match function. Returns 0 if not found.

	static inline PrynTag *FindById (PrynTagList *list, const PrynString &id) { PrynTag *tag; prynCheckErrorNull (prynTagFindById (&tag, list, &id)); return tag; } ///< Find a tag, potentially matching only against tags with no key and no match function. Returns 0 if not found.

	static inline void *FindDataByMatch (PrynTagList *list, const PrynString &id, void *key) { void *result; prynCheckErrorNull (prynTagFindDataByMatch (&result, list, &id, key)); return result; } ///< Find a tag, potentially matching only against tags with match functions. Returns the data value for the match or 0 if not found.

	static inline void *FindDataByKey (PrynTagList *list, void *key) { void *result; prynCheckErrorNull (prynTagFindDataByKey (&result, list, key)); return result; } ///< Find a tag, potentially matching only against tags with a key and no match function. Returns the data value for the match or 0 if not found.

	static inline void *FindDataById (PrynTagList *list, const PrynString &id) { void *result; prynCheckErrorNull (prynTagFindDataById (&result, list, &id)); return result; } ///< Find a tag, potentially matching only against tags with no key and no match function. Returns the data value for the match or 0 if not found.

	inline PrynResult checkError (PrynResult result) { return prynCheckErrorBase (state (), result); } ///< If the result is below zero, throw an error.

	static inline PrynTagDestroyFunction FreeDataFunction () { return prynTagFreeDataFunction (); } ///< Return a function for destroying a tag by deleting the data normally.

	inline void destroy () { prynTagDestroy (this); } ///< Destroy the tag. After calling this function, this tag pointer must not be used.

/** @name Properties
@{ */

	inline PrynState *state () { PrynState *state; prynCheckErrorNull (prynTagState (this, &state)); return state; } ///< The state for the tag.
	inline PrynObject object () { PrynObject result; checkError (prynTagToObject (this, &result)); return result; } ///< The object the tag is within.
	inline PrynTagList *list () { PrynTagList *result; checkError (prynTagList (this, &result)); return result; } ///< The list of tags within the object this tag is within.
	inline void *data () { void *result; checkError (prynTagData (this, &result)); return result; } ///< The tag data.
	inline void data (void *value) { checkError (prynTagSetData (this, value)); } ///< Set the data value on the tag.
	inline void *key () { void *result; checkError (prynTagKey (this, &result)); return result; } ///< The tag key, or 0 if none was given.
	inline void key (void *value) { checkError (prynTagSetKey (this, value)); } ///< Set the key on the tag. This does not set match to 0. If value is 0, it will revert to id matching unless if there's a #matchFunction.
	inline PrynTagList *tags () { PrynTagList *result; checkError (prynTagTags (this, &result)); return result; } ///< The list of tags applied to this tag.
	inline void matchFunction (PrynTagMatchFunction value) { checkError (prynTagSetMatchFunction (this, value)); } ///< Set the tag match function. If value is 0, it will revert to key matching.
	inline void destroyFunction (PrynTagDestroyFunction value) { checkError (prynTagSetDestroyFunction (this, value)); } ///< Set the function to destroy the tag. This may be 0.

/** @} */

#endif /* __cplusplus */
};
#endif PrynInternalStructs

/** @} */ // (group)

#endif /* PRYN_TAG_H */
